// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package syntax

import (
	"fmt"
	"io"
	"os"
)

// 模式描述解析器模式。
type Mode uint

// 解析器支持的模式。
const (
	CheckBranches Mode = 1 << iota // 检查标签的正确使用、中断、继续和转到语句
	AllowGenerics
	AllowMethodTypeParams // 尚不支持接口方法；如果未设置AllowGenerics，则忽略
)

// 错误描述了语法错误。Error实现了Error接口。
type Error struct {
	Pos Pos
	Msg string
}

func (err Error) Error() string {
	return fmt.Sprintf("%s: %s", err.Pos, err.Msg)
}

var _ error = Error{} // 验证错误是否实现了错误

// 对于读取错误时遇到的每个错误，都会调用一个错误处理程序。去归档。
type ErrorHandler func(err error)

// Pragma值增加了包、导入、常量、func、类型或变量声明。
// 其含义完全取决于PragmaHandler，
// 除了nil用于表示“未看到pragma”
type Pragma interface{}

// PragmaHandler用于在扫描时处理
// 传递当前杂注值，该值开始为零，
// 并返回更新的杂注值。
// 文本是指令，去掉“/”前缀。
// 当前pragma保存在每个包、import、const、func、type或var 
// 声明中，保存到文件、ImportDecl、ConstDecl、fundecl、TypeDecl或VarDecl节点中。
// 
// 如果文本是空字符串，则将pragma返回给未使用的处理程序，这意味着它出现在非声明之前。
// 处理程序可能希望报告错误。在本例中，pos是
// 当前解析器位置，而不是pragma本身的位置。
// Blank指定该行在pragma之前是否为空。
type PragmaHandler func(pos Pos, blank bool, text string, current Pragma) Pragma

// Parse从src解析一个Go源文件，并返回相应的
// 语法树。如果有错误，Parse将返回发现的第一个错误
// 和可能部分构造的语法树，或nil。
// 
// 如果是错误的话！=nil，每次遇到错误都会调用它，Parse将
// 处理尽可能多的源代码。在本例中，如果没有找到正确的package子句，则返回的语法树
// 仅为nil。
// 如果errh为nil，则在遇到第一个
// 错误时，解析将立即终止，返回的语法树为nil。如果布拉格！=nil，每次遇到pragma都会调用它。
// 
func Parse(base *PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, first error) {
	defer func() {
		if p := recover(); p != nil {
			if err, ok := p.(Error); ok {
				first = err
				return
			}
			panic(p)
		}
	}()

	var p parser
	p.init(base, src, errh, pragh, mode)
	p.next()
	return p.fileOrNil(), p.first
}

// ParseFile的行为类似于Parse，但它从命名文件读取源代码。
func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
	f, err := os.Open(filename)
	if err != nil {
		if errh != nil {
			errh(err)
		}
		return nil, err
	}
	defer f.Close()
	return Parse(NewFileBase(filename), f, errh, pragh, mode)
}
